﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace DomainDriver.DomainModeling.StorageManagers.EntityFrameworkStorage
{
    public class EfAggregateUtilities
    {
        public static void AddNestedValues<NESTED_OBJECT>(DbContext context, Expression<Func<ICollection<NESTED_OBJECT>>> nestedObjectsGetterExpression)
            where NESTED_OBJECT : class
        {
            ICollection<NESTED_OBJECT> nestedObjects = nestedObjectsGetterExpression.Compile()();
            foreach (NESTED_OBJECT nestedObject in nestedObjects)
            {
                context.Set<NESTED_OBJECT>().Add(nestedObject);
            }
        }

        public static void UpdateNestedValues<AGGREGATE_ROOT, KEY, NESTED_OBJECT>(DbContext context, AGGREGATE_ROOT updatedAggregateRoot, AGGREGATE_ROOT originalAggregateRoot, Expression<Func<NESTED_OBJECT, KEY>> keyGetterExpression, Expression<Func<NESTED_OBJECT, NESTED_OBJECT, bool>> nestedObjectsMatchExpression, Expression<Func<AGGREGATE_ROOT, IDictionary<KEY, NESTED_OBJECT>>> dictionaryGetterExpression)
            where NESTED_OBJECT : class
        {
            IDictionary<KEY, NESTED_OBJECT> updatedDictionary = dictionaryGetterExpression.Compile()(updatedAggregateRoot);
            IDictionary<KEY, NESTED_OBJECT> originalDictionary = dictionaryGetterExpression.Compile()(originalAggregateRoot);
            IDictionary<KEY, NESTED_OBJECT> entriesToDelete = new Dictionary<KEY, NESTED_OBJECT>(originalDictionary);

            foreach (NESTED_OBJECT value in updatedDictionary.Values)
            {
                KEY keyForValue = keyGetterExpression.Compile()(value);

                if (!entriesToDelete.ContainsKey(keyForValue))
                {
                    context.Set<NESTED_OBJECT>().Add(value);
                    continue;
                }
                // NOTE: Entity Framework should handle updates to values automatically
                entriesToDelete.Remove(keyForValue);
            }

            foreach (NESTED_OBJECT valueToDelete in entriesToDelete.Values)
            {
                Expression<Func<NESTED_OBJECT, bool>> queryExpression = ((NESTED_OBJECT val) => nestedObjectsMatchExpression.Compile()(val, valueToDelete));
                IEnumerable<NESTED_OBJECT> queryResults = context.Set<NESTED_OBJECT>().Where<NESTED_OBJECT>(queryExpression.Compile());

                if (queryResults.Count<NESTED_OBJECT>() > 0)
                {
                    NESTED_OBJECT matchingValue = queryResults.First<NESTED_OBJECT>();
                    context.Set<NESTED_OBJECT>().Remove(matchingValue);
                }
            }
        }

        public static void DeleteNestedValues<NESTED_OBJECT>(DbContext context, Expression<Func<NESTED_OBJECT, bool>> deleteQueryExpression)
            where NESTED_OBJECT : class
        {
            IEnumerable<NESTED_OBJECT> deleteQueryResults = context.Set<NESTED_OBJECT>().Where<NESTED_OBJECT>(deleteQueryExpression.Compile());
            List<NESTED_OBJECT> nestedObjectsToDelete = new List<NESTED_OBJECT>(deleteQueryResults);
            foreach (NESTED_OBJECT nestedObject in nestedObjectsToDelete)
            {
                context.Set<NESTED_OBJECT>().Remove(nestedObject);
            }
        }
    }
}